#include "config.h"

#include <arpa/inet.h>
#include <errno.h>

#define DBG_SUBSYS S_LIBINTERFACE

#include "job_dock.h"
#include "net_global.h"
#include "rpc/auth.h"
#include "sunrpc_proto.h"
#include "../net/net_events.h"
#include "ynet_rpc.h"
#include "nfs_events.h"
#include "dbg.h"

#define NFS3_WRITE 7

int sunrpc_pack_len(void *buf, uint32_t len, int *msg_len, int *io_len)
{
        uint32_t *length, _len, credlen, verilen, headlen;
        sunrpc_request_t *req;
        auth_head_t *cred, *veri;
        void *msg;

        if (len < sizeof(sunrpc_request_t)) {
                DERROR("less then sunrpc_head_t\n");

                return 0;
        }

        length = buf;

        DBUG("sunrpc request len %u, is last %u\n", ntohl(*length) ^ (1 << 31),
             ntohl(*length) & (1 << 31));

        if (ntohl(*length) & (1 << 31)) {
                _len = (ntohl(*length) ^ (1 << 31)) + sizeof(uint32_t);
        } else {
                _len = (ntohl(*length)) + sizeof(uint32_t);
        }

        req = buf;

        if (ntohl(req->procedure) == NFS3_WRITE) {
#if 1
                (void) msg;
                (void) veri;
                (void) cred;
                (void) headlen;
                (void) verilen;
                (void) credlen;

                *io_len = (_len / 1024) * 1024;
                *msg_len = _len - *io_len;
#else
                left = len - sizeof(sunrpc_request_t);

                cred = buf + sizeof(*req);
                credlen = ntohl(cred->length);

                veri = (void *)cred + credlen + sizeof(*cred);
                verilen = ntohl(veri->length);

                msg = (void *)veri + verilen + sizeof(*veri);

                headlen =  msg - buf + sizeof(fileid_t) + sizeof(uint64_t)
                        + sizeof(uint32_t)  * 4;

                *msg_len = headlen;
                *io_len = _len - headlen;

                YASSERT(_len > headlen);
                DBUG("nfs write msg %u io %u\n", *msg_len, *io_len);
#endif
        } else {
                *msg_len =  _len;
                *io_len = 0;
        }

        return 0;
}

typedef struct {
        sunrpc_request_t req;
        sockid_t sockid;
        buffer_t buf;
} __sunrpc_request_t;

static void __sunrpc_func(void *arg)
{
        __sunrpc_request_t *__sunrpc_request;
        sunrpc_request_t *req;

        __sunrpc_request = arg;
        req = &__sunrpc_request->req;

        if (req->program == MOUNTPROG && (req->progversion == MOUNTVERS3 ||
                                          req->progversion == MOUNTVERS1)) {
                DBUG("mount\n");
                
                mnt_event_handler(&__sunrpc_request->sockid, req, &__sunrpc_request->buf);
        } else if (req->program == NFS3_PROGRAM && req->progversion == NFS_V3) {
                DBUG("nfs\n");
                nfs_event_handler(&__sunrpc_request->sockid, req, &__sunrpc_request->buf);
        } else if (req->program == ACL_PROGRAM) {
                DBUG("acl\n");
                acl_event_handler(&__sunrpc_request->sockid, req, &__sunrpc_request->buf);
        } else if (req->program == NLM_PROGRAM) {
                UNIMPLEMENTED(__DUMP__);
                //nlm_event_handler(&__sunrpc_request->sockid, req, &__sunrpc_request->buf);
        }else{
                DERROR("we got wrong prog %u v%u, halt\n", req->program,
                       req->progversion);  //XXX: handle this --gray
        }


        mbuffer_free(&__sunrpc_request->buf);
        yfree((void **)&arg);
}

int sunrpc_pack_handler(const sockid_t *sockid, buffer_t *buf)
{
        int ret;
        sunrpc_proto_t type;
        sunrpc_request_t *req;
        auth_head_t cred, veri;
        __sunrpc_request_t *__sunrpc_request;
        char credbuf[MAX_AUTH_BYTES];
        char veribuf[MAX_AUTH_BYTES];
        uint32_t is_last;

        mbuffer_get(buf, &type, sizeof(sunrpc_proto_t));

        type.msgtype = ntohl(type.msgtype);

        DBUG("get sunrpc pack %d msg %d id %u\n", ntohl(type.length) ^ (1 << 31),
              type.msgtype, ntohl(type.xid));

        switch (type.msgtype) {
        case SUNRPC_REQ_MSG:
                ret = ymalloc((void **)&__sunrpc_request, sizeof(*__sunrpc_request));
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                req = &__sunrpc_request->req;

                ret = mbuffer_popmsg(buf, req, sizeof(sunrpc_request_t));
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                is_last = ntohl(req->length) & (1 << 31);

                if (is_last)
                        req->length = ntohl(req->length) ^ (1 << 31);
                else
                        req->length = ntohl(req->length);

                req->xid = req->xid;
                req->msgtype = ntohl(req->msgtype);
                req->rpcversion = ntohl(req->rpcversion);
                req->program = ntohl(req->program);
                req->progversion = ntohl(req->progversion);
                req->procedure = ntohl(req->procedure);

                if (is_last != (uint32_t)(1 << 31)) {
                        DERROR("%u:%u\n", is_last, (uint32_t)(1 << 31));

                        DERROR("rpc request len %u xid %u version %u prog %u version"
                               " %u procedure %u\n", req->length, req->xid,
                               req->rpcversion, req->program, req->progversion,
                               req->procedure);

                        UNIMPLEMENTED(__DUMP__);
                }

                DBUG("rpc request len %u xid %u version %u prog %u version"
                     " %u procedure %u\n", req->length, req->xid,
                     req->rpcversion, req->program, req->progversion,
                     req->procedure);

                mbuffer_get(buf, &cred, sizeof(auth_head_t));
                cred.flavor = ntohl(cred.flavor);
                cred.length = ntohl(cred.length);

                ret = mbuffer_popmsg(buf, credbuf, cred.length + sizeof(auth_head_t));
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                mbuffer_get(buf, &veri, sizeof(auth_head_t));
                veri.flavor = ntohl(veri.flavor);
                veri.length = ntohl(veri.length);

                DBUG("cred %u len %u veri %u len %u\n", cred.flavor, cred.length,
                     veri.flavor, veri.length);

                ret = mbuffer_popmsg(buf, veribuf, veri.length + sizeof(auth_head_t));
                if (unlikely(ret))
                        UNIMPLEMENTED(__DUMP__);

                mbuffer_init(&__sunrpc_request->buf, 0);
                mbuffer_merge(&__sunrpc_request->buf, buf);

                DBUG("request len %u\n", __sunrpc_request->buf.len);

                __sunrpc_request->sockid = *sockid;

                schedule_task_new("none", __sunrpc_func, __sunrpc_request, -1);

                break;
        case SUNRPC_REP_MSG:
                DERROR("bad msgtype\n");
                YASSERT(0);
        default:
                DERROR("bad msgtype\n");
                YASSERT(0);
        }

        return 0;
}

#if 0
int sunrpc_accept_handler(void *_sock, void *ctx)
{
        int ret;
        net_proto_t proto;
        net_handle_t nh;
        ynet_sock_conn_t *sock = _sock;

        (void) ctx;

        DBUG("new conn for sd %d\n", sock->nh.u.sd.sd);

        _memset(&proto, 0x0, sizeof(net_proto_t));

        proto.head_len = sizeof(sunrpc_request_t);
        proto.reader = net_events_handle_read;
        proto.writer = net_events_handle_write;
        proto.pack_len = sunrpc_pack_len;
        proto.pack_handler = sunrpc_pack_handler;
        //proto.prog[0].handler = sunrpc_request_handler;
        //proto.jobtracker = &sunrpc_jobtracker;

        ret = sdevent_accept(sock->nh.u.sd.sd, &nh, &proto,
                              YNET_RPC_NONBLOCK);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sdevent_add(&nh, Y_EPOLL_EVENTS, NULL, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}
#endif
